﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using RFEngine.Entities;
using RFEngine.Graphics.Interfaces;

namespace RFEngine.Graphics
{
    public delegate void SpriteEvent(Sprite sprite);

    public class Sprite : PositionedObject, IDrawableEntity, ITextureable
    {
        #region Fields

        private bool _EnableUpdates = true;
        private bool _EnableDrawing = true;
        private bool _TextureStateChanged;
        private bool _ColorChanged;

        private float _AccumulationTimer = 0.0f;
        private float _UpdateTime = 1.0f / 60.0f;

        private float _Red = 1.0f;
        private float _Green = 1.0f;
        private float _Blue = 1.0f;
        private float _Alpha = 1.0f;

        private float _AlphaRate;
        private float _RedRate;
        private float _GreenRate;
        private float _BlueRate;

        private float _XScaleRate;
        private float _YScaleRate;        

        private Vector2 _Scale = Vector2.One;        
        private Color _Color = Color.White;
        
        private Texture2D _Texture;
        private BoundingBox _BoundingBox;
        private BoundingFrustum _BoundingFrustum;
        private TexturedQuad _TexturedQuad;
        private TextureState _TextureState = TextureState.Normal;

        private Vector2 _TempVector2 = Vector2.Zero;
        private Vector3 _TempVector3 = Vector3.Zero;
        private Vector3 _BoudingBoxMin = -Vector3.One;
        private Vector3 _BoudingBoxMax = Vector3.One;

        /// <summary>
        /// Sets a value indicating this object uses a CustomDraw Method to Draw this entity.
        /// </summary>
        public event SpriteEvent CustomDrawEvent;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or Sets a value indicating this object should update.
        /// </summary>
        public bool EnableUpdates
        {
            get { return _EnableUpdates; }
            set { _EnableUpdates = value; }
        }

        /// <summary>
        /// Gets or Sets a value indicating this object should draw.
        /// </summary>
        public bool EnableDrawing
        {
            get { return _EnableDrawing; }
            set { _EnableDrawing = value; }
        }

        /// <summary>
        /// Gets or Sets the red color component to use when tinting this object.
        /// </summary>
        public virtual float Red
        {
            get { return _Red; }
            set
            {
                _Red = value;

                if (_Red > 1.0f)
                    _Red = 1.0f;

                else if (_Red < 0.0f)
                    _Red = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0xff00ffff) | ((uint)(255f * _Red) << 0x10);
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the red color component in units per second.
        /// </summary>
        public virtual float RedRate
        {
            get { return _RedRate; }
            set { _RedRate = value; }
        }

        /// <summary>
        /// Gets or Sets the green color component to use when tinting this object.
        /// </summary>
        public virtual float Green
        {
            get { return _Green; }
            set
            {
                _Green = value;

                if (_Green > 1.0f)
                    _Green = 1.0f;

                else if (_Green < 0.0f)
                    _Green = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0xffff00ff) | ((uint)(255f * _Green) << 0x8);
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the green color component in units per second.
        /// </summary>
        public virtual float GreenRate
        {
            get { return _GreenRate; }
            set { _GreenRate = value; }
        }

        /// <summary>
        /// Gets or Sets the blue color component to use when tinting this object.
        /// </summary>
        public virtual float Blue
        {
            get { return _Blue; }
            set
            {
                _Blue = value;
                if (_Blue > 1.0f)
                    _Blue = 1.0f;

                if (Blue < 0.0f)
                    _Blue = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0xffffff00) | ((uint)(255f * _Blue));
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the blue color component in units per second.
        /// </summary>
        public virtual float BlueRate
        {
            get { return _BlueRate; }
            set { _BlueRate = value; }
        }

        /// <summary>
        /// Gets or Sets the alpha color component to use when tinting this object.
        /// </summary>
        public virtual float Alpha
        {
            get { return _Alpha; }
            set
            {
                _Alpha = value;

                if (_Alpha > 1.0f)
                    _Alpha = 1.0f;

                else if (_Alpha < 0.0f)
                    _Alpha = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0x00ffffff) | ((uint)(255f * _Alpha) << 0x18);
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the alpha color component in units per second.
        /// </summary>
        public virtual float AlphaRate
        {
            get { return _AlphaRate; }
            set { _AlphaRate = value; }
        }

        /// <summary>
        /// Gets or Sets the value of the texture coordinates of the top drawing vertices.
        /// </summary>
        public virtual float TopTextureCoordinate
        {
            get { return _TexturedQuad.TopTextureCoordinate; }
            set
            { 
                _TexturedQuad.TopTextureCoordinate = value;
                _TextureStateChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the value of the texture coordinates of the bottom drawing vertices.
        /// </summary>
        public virtual float BottomTextureCoordinate
        {
            get { return _TexturedQuad.BottomTextureCoordinate; }
            set
            {
                _TexturedQuad.BottomTextureCoordinate = value;
                _TextureStateChanged = true;
            }
        }
        
        /// <summary>
        /// Gets or Sets the value of the texture coordinates of the left drawing vertices.
        /// </summary>
        public virtual float LeftTextureCoordinate
        {
            get { return _TexturedQuad.LeftTextureCoordinate; }
            set
            {
                _TexturedQuad.LeftTextureCoordinate = value;
                _TextureStateChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the value of the texture coordinates of the right drawing vertices.
        /// </summary>
        public virtual float RightTextureCoordinate
        {
            get { return _TexturedQuad.RightTextureCoordinate; }
            set
            {
                _TexturedQuad.RightTextureCoordinate = value;
                _TextureStateChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the scale of this object along the x-axis in world space.
        /// </summary>
        public virtual float XScale
        {
            get { return _Scale.X; }
            set { _Scale.X = value; }
        }

        /// <summary>
        /// Gets or Sets the rate at which this object is scaled along the x-axis in world space in units per second.
        /// </summary>
        public virtual float XScaleRate
        {
            get { return _XScaleRate; }
            set { _XScaleRate = value; }
        }

        /// <summary>
        /// Gets or Sets the scale of this object along the y-axis in world space.
        /// </summary>
        public virtual float YScale
        {
            get { return _Scale.Y; }
            set { _Scale.Y = value; }
        }

        /// <summary>
        /// Gets or Sets the rate at which this object is scaled along the y-axis in world space in units per second.
        /// </summary>
        public virtual float YScaleRate
        {
            get { return _YScaleRate; }
            set { _YScaleRate = value; }
        }

        /// <summary>
        /// Gets the color tint of this object.
        /// </summary>
        public virtual Color Color
        {
            get { return _Color; }
        }

        /// <summary>
        /// Gets the scaling vector of this object in world space.
        /// </summary>
        public virtual Vector2 Scale
        {
            get { return _Scale; }
        }

        /// <summary>
        /// Gets or Sets the texture used by this object.
        /// </summary>
        public virtual Texture2D Texture
        {
            get { return _Texture; }
            set { _Texture = value; }
        }

        /// <summary>
        /// Gets or Set how this texture is drawn.
        /// </summary>
        public virtual TextureState TextureState
        {
            get { return _TextureState; }
            set
            {
                _TextureState = value;
                _TextureStateChanged = true;
            }                
        }

        #endregion

        #region Methods

        #region Constructor(s)

        /// <summary>
        /// Constructs a new instance of this object. and scales to texture.
        /// </summary>
        /// <param name="texture">The texture this object will use.</param>
        public Sprite(Texture2D texture) 
        {
            _Texture = texture;
            _TexturedQuad = new TexturedQuad();
            _BoundingBox = new BoundingBox(new Vector3(-1, -1, -1), new Vector3(1, 1, 1));

            UpdateBoudingBox();
        }

        #endregion

        /// <summary>
        /// Method used for setting the base scale of this object, in relation to the Texture aspect ratio;
        /// </summary>
        public void ScaleToTexture()
        {
            _TexturedQuad.AspectRatio = (float)_Texture.Width / (float)(_Texture.Height);
        }

        /// <summary>
        /// Method used for setting the base scale of this object, in relation to units in world space. (1 x 1)
        /// </summary>
        public void ScaleToUnits()
        {
            _TexturedQuad.AspectRatio = 1.0f;
        }

        /// <summary>
        /// Method for Updating this object.
        /// </summary>
        /// <param name="updateTime">The amount of time in seconds since the last update.</param>
        public virtual void Update(float updateTime)
        {
            if (EnableUpdates)
            {
                #region Update Position

                if (Velocity.X != 0 || Velocity.Y != 0 || Velocity.Z != 0)
                {
                    X += Velocity.X * updateTime;
                    Y += Velocity.Y * updateTime;
                    Z += Velocity.Z * updateTime;
                }

                #endregion

                #region Update Rotation

                if (XRotationRate != 0 || YRotationRate != 0 || ZRotationRate != 0)
                {
                    XRotation += XRotationRate * 360.0f * updateTime;
                    YRotation += YRotationRate * 360.0f * updateTime;
                    ZRotation += ZRotationRate * 360.0f * updateTime;
                }

                #endregion

                #region Update Scale

                if (XScaleRate != 0 || YScaleRate != 0)
                {
                    _Scale.X += XScaleRate * updateTime;
                    _Scale.Y += YScaleRate * updateTime;
                }

                #endregion

                if (_RedRate != 0 || _GreenRate != 0 || _BlueRate != 0 || _AlphaRate != 0)
                {
                    Red += _RedRate * updateTime;
                    Green += _GreenRate * updateTime;
                    Blue += _BlueRate * updateTime;
                    Alpha += _AlphaRate * updateTime;
                }

                if (Position != _TexturedQuad.Position || Rotation != _TexturedQuad.Rotation || Scale != _TexturedQuad.Scale)
                {
                    _TexturedQuad.SetPositionRotationScale(ref _Position, ref _Rotation, ref _Scale);
                    UpdateBoudingBox();
                }

                if (_ColorChanged)
                {
                    _TexturedQuad.SetColor(ref _Color);
                    _ColorChanged = false;
                }

                if (_TextureStateChanged)
                {
                    _TexturedQuad.UpdateTextureCoords(_TextureState);
                    _TextureStateChanged = false;
                }
            }
        }

        /// <summary>
        /// Method for Determining if this object is within the bounds of the view.
        /// </summary>
        /// <param name="view">A Matrix value describing the view.</param>
        /// <param name="projection">A Matrix value describing the projection of the view.</param>
        /// <returns>Returns <typeparamref name="true"/> if this object is within the bounds of the view, otherwise <typeparamref name="false"/></returns>
        public bool IsInView(ref Matrix view, ref Matrix projection)
        {
            if (_BoundingFrustum == null)
                _BoundingFrustum = new BoundingFrustum(view * projection);

            _BoundingFrustum.Matrix = view * projection;

            return IsInView(ref _BoundingFrustum);
        }

        /// <summary>
        /// Method for Determining if this object is within the bouds of the view.
        /// </summary>
        /// <param name="boundingFrustum">The bounding frustum to test against.</param>
        /// <returns>Returns <typeparamref name="true"/> if this object is within the bounds of the view, otherwise <typeparamref name="false"/></returns>
        public bool IsInView(ref BoundingFrustum boundingFrustum)
        {
            _BoundingFrustum = boundingFrustum;

            if (boundingFrustum.Contains(_BoundingBox) == ContainmentType.Disjoint)
                return false;

            return true;
        }

        /// <summary>
        /// Method for getting a value indicating if this object uses a custom drawing event.
        /// </summary>
        /// <returns>Returns true if this object uses a custom drawing event, otherwise false.</returns>
        public bool UsesCustomDraw()
        {
            return (CustomDrawEvent != null);
        }

        /// <summary>
        /// Method for Drawing this instance.
        /// <remarks>
        /// This Object does not set states, or its Texture, just draws it's verts. Its upto you to set the Effect states before
        /// passing the effect to this object.
        /// </remarks>
        /// </summary>
        /// <param name="effect">The effect to use when drawing this object.</param>
        public void Draw(Effect effect)
        {
            int primitives = _TexturedQuad.VerticesForDrawing.Length / 3;
            
            if (primitives > 0)
                effect.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, _TexturedQuad.VerticesForDrawing, 0, primitives);
        }
        
        #region Private Methods

        /// <summary>
        /// Method for updating the bouding box of this object.
        /// </summary>
        private void UpdateBoudingBox()
        {
            _BoudingBoxMin = GetVector3(0.0f, 0.0f, 0.0f);
            _BoudingBoxMax = GetVector3(0.0f, 0.0f, 0.0f);

            foreach (SpriteVertex vertex in _TexturedQuad.VerticesForDrawing)
            {
                if (vertex.Position.X < _BoudingBoxMin.X)
                    _BoudingBoxMin.X = vertex.Position.X;

                if (vertex.Position.X > _BoudingBoxMax.X)
                    _BoudingBoxMax.X = vertex.Position.X;

                if (vertex.Position.Y < _BoudingBoxMin.Y)
                    _BoudingBoxMin.Y = vertex.Position.Y;

                if (vertex.Position.Y > _BoudingBoxMax.Y)
                    _BoudingBoxMax.Y = vertex.Position.Y;

                if (vertex.Position.Z < _BoudingBoxMin.Z)
                    _BoudingBoxMin.Z = vertex.Position.Z - .1f;

                if (vertex.Position.Z > _BoudingBoxMax.Z)
                    _BoudingBoxMax.Z = vertex.Position.Z + .1f;
            }
        }
        
        #endregion

        #region Protected Methods

        /// <summary>
        /// Method for getting a new Vector2 without creating a new instance.
        /// </summary>
        /// <param name="x">The value along the x-axis in world space.</param>
        /// <param name="y">The value along the y-axis in world space.</param>
        /// <returns>A position in world space.</returns>
        protected Vector2 GetVector2(float x, float y)
        {
            _TempVector2.X = x;
            _TempVector2.Y = y;

            return _TempVector2;
        }

        /// <summary>
        /// Method for getting a new Vector2 without creating a new instance.
        /// </summary>
        /// <param name="x">The value along the x-axis in world space.</param>
        /// <param name="y">The value along the y-axis in world space.</param>
        /// <param name="z">The value along the z-axis in world space.</param>
        /// <returns>A position in world space.</returns>                
        protected Vector3 GetVector3(float x, float y, float z)
        {
            _TempVector3.X = x;
            _TempVector3.Y = y;
            _TempVector3.Z = z;

            return _TempVector3;
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Method used for Custom Drawing.
        /// </summary>
        internal void CustomDraw()
        {
            if (UsesCustomDraw())
                CustomDrawEvent(this);
        }

        #endregion

        #endregion
    }
}
